package com.leisure.utils;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.time.LocalDateTime;
import java.util.*;

/**
 * ClassName: RSAUtil
 * Package: com.leisure.utils
 * Description:
 *
 * @Author: MyLeisureLife
 * @Date: 2024/6/5:12:42:30 星期三
 */
public class RSAUtil {
    //签名算法名称
    private static final String RSA_KEY_ALGORITHM = "RSA";

    //标准签名算法名称
    private static final String RSA_SIGNATURE_ALGORITHM = "SHA1withRSA";
    private static final String RSA2_SIGNATURE_ALGORITHM = "SHA256withRSA";

    //RSA密钥长度,默认密钥长度是1024,密钥长度必须是64的倍数，在512到65536位之间,不管是RSA还是RSA2长度推荐使用2048
    private static final int KEY_SIZE = 2048;

    // rsa秘钥对存储位置
    private static final String pairKeyDirectory = "src/main/resources/rsa";
    // rsa 公钥存储位置
    private static final String publicKeyPath = "src/main/resources/rsa/publicKeyStr.pem";
    // rsa 私钥存储位置
    private static final String privateKeyPath = "src/main/resources/rsa/privateKeyStr.pem";

    /**
     * 创建（公钥 和 私钥）秘钥对,并将公钥 和 私钥存储在pem文件中
     */
    public static void generateKeyPair() {
        KeyPairGenerator keyPairGenerator;
        try {
            //创建秘钥对生成器
            keyPairGenerator = KeyPairGenerator.getInstance(RSA_KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("RSA初始化密钥出现错误,算法异常");
        }

        // 创建 加密强随机数生成器（RNG） 是一种设计用于产生难以预测的随机数序列的算法或设备。
        SecureRandom secureRandom = new SecureRandom();
        // 设置当前时间为种子来源，确保生成的随机数序列难以被外部预测。
        secureRandom.setSeed(LocalDateTime.now().toString().getBytes());

        //初始化密钥生成器
        keyPairGenerator.initialize(KEY_SIZE, secureRandom);
        //使用秘钥生成器生成秘钥对
        KeyPair keyPair = keyPairGenerator.genKeyPair();

        // 从秘钥对中 获取公钥
        byte[] pub_key = keyPair.getPublic().getEncoded();

        //从秘钥对中 获取私钥
        byte[] pri_key = keyPair.getPrivate().getEncoded();


        // 文件夹不存在，则先创建
        try {
            Files.createDirectories(Paths.get(pairKeyDirectory));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        try(
            // 私钥pem写入路径
            FileWriter priavteKeyFileWriter = new FileWriter(privateKeyPath);
            PemWriter privateKeyPemWriter = new PemWriter(priavteKeyFileWriter);
            // 公钥pem写入路径
            FileWriter publicKeyFileWriter = new FileWriter(publicKeyPath);
            PemWriter publicKeyPemWriter = new PemWriter(publicKeyFileWriter);
        ) {
            // 使用私钥路径添加私钥的pem文件，会被base64编码
            PemObject privateKeyPem  = new PemObject("PRIVATE KEY", pri_key);
            privateKeyPemWriter.writeObject(privateKeyPem);
            // 使用公钥路径添加公钥的pem文件，会被base64编码
            PemObject publicKeyPem  = new PemObject("PUBLIC KEY", pub_key);
            publicKeyPemWriter.writeObject(publicKeyPem);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 读取公钥pem文件生成公钥
     * @return 公钥
     */
    public static PublicKey getPublicKeyByPemFile(){
        try (
            // 公钥pem读取路径
            Reader publicKeyReader = new FileReader(publicKeyPath);
            PemReader publicKeyPemReader = new PemReader(publicKeyReader)
        ){
            PemObject publicKeyPem = publicKeyPemReader.readPemObject();
            // 获取公钥字节码
            byte[] publicKeyBytes =  publicKeyPem.getContent();
            // 获取秘钥RSA制造厂
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            // 设置秘钥编码规范，让秘钥制造厂知道你的编码规范，才能创建相应的秘钥，是创建公钥对象还是私钥对象，X509是用于公钥的
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicKeyBytes);
            // RSA秘钥制造厂，创建公钥
            PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
            return publicKey;
        }catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e){
            throw new RuntimeException(e);
        }

    }

    /**
     * 读取私钥pem文件生成私钥
     * @return 私钥
     */
    public static PrivateKey getPrivateKeyByPemFile(){
        try (
                // 公钥pem读取路径
                Reader privateKeyReader = new FileReader(privateKeyPath);
                PemReader privateKeyPemReader = new PemReader(privateKeyReader)
        ){
            PemObject privateKeyPem = privateKeyPemReader.readPemObject();
            // 获取公钥字节码
            byte[] privateKeyBytes =  privateKeyPem.getContent();
            // 获取秘钥RSA制造厂
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            // 设置秘钥编码规范，让秘钥制造厂知道你的编码规范，才能创建相应的秘钥，是创建公钥对象还是私钥对象，PKCS8是用于私钥的
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
            // RSA秘钥制造厂，创建公钥
            PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
            return privateKey;
        }catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e){
            throw new RuntimeException(e);
        }

    }

    /**
     * 使用公钥对数据进行加密处理
     * @param data 数据
     * @return 加密后的数据
     */
    public static String encryptByPublicKey(String data){
        // 获取公钥
        PublicKey publicKey = getPublicKeyByPemFile();

        try {
            //使用Cipher加密
            Cipher cipher = Cipher.getInstance("RSA");
            //初始化cipher，用于加密，提供加密的秘钥
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            //开始加密
            byte[] publicKeyEncryptData = cipher.doFinal(data.getBytes());
            String publicKeyEncryptDataStr =  Base64.getEncoder().encodeToString(publicKeyEncryptData);
            return publicKeyEncryptDataStr;
        } catch (InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException |
                 BadPaddingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 使用私钥对数据进行加密处理
     * @param data 数据
     * @return 加密后的数据
     */
    public static String encryptByPrivateKey(String data){
        // 获取私钥
        PrivateKey privateKey = getPrivateKeyByPemFile();

        try {
            //使用Cipher加密算法
            Cipher cipher = Cipher.getInstance("RSA");
            //初始化cipher，用于加密，提供加密的秘钥
            cipher.init(Cipher.ENCRYPT_MODE, privateKey);
            //开始加密
            byte[] privateKeyEncryptData = cipher.doFinal(data.getBytes());
            // 将加密后的数据变为base64数据
            return Base64.getEncoder().encodeToString(privateKeyEncryptData);
        } catch (InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException |
                 BadPaddingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据公钥解码
     * @param base64Data 私钥加密数据
     * @return 正常数据
     */
    public static String decryptByPublicKey(String base64Data){
        // 获取公钥
        PublicKey publicKey = getPublicKeyByPemFile();

        try {
            //使用Cipher加密
            Cipher cipher = Cipher.getInstance("RSA");
            //初始化cipher，用于加密，提供加密的秘钥
            cipher.init(Cipher.DECRYPT_MODE, publicKey);
            //开始解密
            byte[] data = Base64.getDecoder().decode(base64Data);
            byte[] publicKeyDecryptData = cipher.doFinal(data);
            return new String(publicKeyDecryptData);
        } catch (InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException |
                 BadPaddingException e) {
            throw new RuntimeException(e);
        }

    }


    /**
     * 根据私钥解码
     * @param base64Data 公钥加密数据
     * @return 正常数据
     */
    public static String decryptByPrivateKey(String base64Data){
        // 获取私钥
        PrivateKey privateKey = getPrivateKeyByPemFile();

        try {
            //使用Cipher加密
            Cipher cipher = Cipher.getInstance("RSA");
            //初始化cipher，用于加密，提供加密的秘钥
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            // 获取没有base编码的加密数据
            byte[] data = Base64.getDecoder().decode(base64Data);
            //开始解密
            byte[] publicKeyDecryptData = cipher.doFinal(data);
            return new String(publicKeyDecryptData);
        } catch (InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException | IllegalBlockSizeException |
                 BadPaddingException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * RSA签名
     * @param data 待签名数据
     * @param signatureType RSA或RSA2
     * @return 签名
     */
    public static String signature(byte[] data, String signatureType){
        // 获取私钥
        PrivateKey privateKey = getPrivateKeyByPemFile();
        //标准签名算法名称(RSA还是RSA2)
        String algorithm = RSA_KEY_ALGORITHM.equals(signatureType) ? RSA_SIGNATURE_ALGORITHM : RSA2_SIGNATURE_ALGORITHM;
        try {
            // 用指定算法产生签名对象Signature
            Signature signature = Signature.getInstance(algorithm);
            //用私钥初始化签名对象Signature
            signature.initSign(privateKey);
            //将待签名的数据传送给签名对象
            signature.update(data);
            //返回签名结果字节数组
            byte[] sign = signature.sign();
            //返回Base64编码后的字符串
            return Base64.getEncoder().encodeToString(sign);
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 验证签名
     * @param data 需要验证的数据
     * @param sign 签名数据
     * @param signatureType 签名类型
     * @return true 文件正常没有改动，false文件已经被改动
     */
    public static boolean verify(byte[] data, byte[] sign, String signatureType){
        // 获取公钥
        PublicKey publicKey = getPublicKeyByPemFile();
        //标准签名算法名称(RSA还是RSA2)
        String algorithm = RSA_KEY_ALGORITHM.equals(signatureType) ? RSA_SIGNATURE_ALGORITHM : RSA2_SIGNATURE_ALGORITHM;

        try {
            //用指定算法产生签名对象Signature
            Signature signature = Signature.getInstance(algorithm);
            //用公钥初始化签名对象,用于验证签名
            signature.initVerify(publicKey);
            //更新签名内容
            signature.update(data);
            //得到验证结果
            return signature.verify(sign);
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        }

    }


    public static void main(String[] args) {
        generateKeyPair();
        System.out.println(getPublicKeyByPemFile());
        System.out.println(11111);
        System.out.println(getPrivateKeyByPemFile());
        String one = encryptByPublicKey("1231");
        String two = encryptByPublicKey("1231");
        System.out.println("加密后的数据：" +encryptByPublicKey("1231"));
        String data = encryptByPrivateKey("1231");
        System.out.println(one);
        System.out.println(two);
        System.out.println(decryptByPublicKey(data));
        System.out.println(decryptByPrivateKey(one));
        System.out.println(decryptByPrivateKey(two));
        String three = signature("123".getBytes(),RSA2_SIGNATURE_ALGORITHM);
        System.out.println(signature("123".getBytes(),RSA2_SIGNATURE_ALGORITHM));
        System.out.println(verify("1234".getBytes(),Base64.getDecoder().decode(three), RSA2_SIGNATURE_ALGORITHM));
    }
}
